/*
 * Copyright 2014 JootMir Project
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * Support: http://www.jootmir.org
 */
package org.jootmir.client.core.font;

import java.awt.Color;
import java.awt.FontFormatException;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.jootmir.client.core.Application;
import org.jootmir.client.core.Files;
import org.jootmir.client.core.OpenGL;
import org.jootmir.client.core.exception.JMir2Exception;
import org.jootmir.client.core.texture.Texture;
import org.lwjgl.opengl.EXTTextureMirrorClamp;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GLContext;

/**
 * 真体字
 * <br>
 * 依托于Java本身的强大字体功能构造我们基于纹理图片的静态字体
 * @see #loadCharacters(FontLoadProgressChangedEventListener)
 * @see #isReadyForUse()
 * 
 * @author ShawRyan
 */
public class AWTFont implements Font {
	/** log */
	private static final Logger log = Logger.getLogger(AWTFont.class);
	/** 纹理宽度 */
	private static final int TEXTURE_WIDTH = 1024;
	/** 纹理高度 */
	private static final int TEXTURE_HEIGHT = 1024;
	/** 显卡绘制 */
	private OpenGL gl = OpenGL.get();
	
	/** 中日韩统一表意文字<br>从\u4E00到\u9FA5 */
	private static final char[] CJK_unified_ideographs = new char[0x9FA6 - 0x4E00];
	static {
		for(int i = 0x4E00, j = 0; i < 0x9FA6; ++i) {
			CJK_unified_ideographs[j++] = (char) i;
		}
	}
	
	/** Java本身的字体 */
	private java.awt.Font awtFont;
	/** 字体的纹理图片存储 */
	private List<Texture> fontTextures = new ArrayList<Texture>(6);
	/** 需要加载的字符 */
	private char[] customChars;
	/** 字体是否已处于初始化中 */
	private volatile boolean loading;
	/** 字体是否已完成初始化 */
	private volatile boolean initialized;
	/** 字体高度(最高的字符，可作为行高) */
	private int fontHeight = 0;
	/** 字符和存放信息 */
	private Map<Character, TextureChar> chars = new HashMap<Character, TextureChar>();
	
	/**
	 * 从系统字体库中创建一个字体
	 * <br>我们会直接使用Java本身的字体函数{@link java.awt.Font#Font(String, int, int) Font}去创建字体
	 * <br>不过构造函数不会进行字符的操作，请自行调用对应函数进行加载{@link #loadCharacters(FontLoadProgressChangedEventListener) loadCharacters}
	 */
	public AWTFont(String name, int style, int size) {
		this(name, style, size, null);
	}
	/**
	 * 从系统字体库中创建一个字体
	 * <br>
	 * 为了加速程序运行，或者是特定情况下不需要加载字符集中所有字体时，我们提供只加载部分字符的功能
	 * <br>
	 * 使用不带<code>additionChars</code>的构造函数时默认加载“{@link #CJK_unified_ideographs 中日韩统一表意文字}”
	 * <br>不过构造函数不会进行字符的操作，请自行调用对应函数进行加载{@link #loadCharacters(FontLoadProgressChangedEventListener) loadCharacters}
	 * 
	 * @param additionChars
	 * 		需要加载的字符
	 */
	public AWTFont(String name, int style, int size, String additionChars) {
		awtFont = new java.awt.Font(name, style, size);
		if(additionChars != null)
			this.customChars = additionChars.toCharArray();
		else {
			this.customChars = CJK_unified_ideographs;
		}
	}
	
	/**
	 * 从磁盘文件创建一个字体
	 * <br>
	 * 我们会直接使用Java本身的字体函数{@link java.awt.Font#createFont(int, java.io.File) createFont}去创建字体并进行下一步操作
	 * <br>不过构造函数不会进行字符的操作，请自行调用对应函数进行加载{@link #loadCharacters(FontLoadProgressChangedEventListener) loadCharacters}
	 * 
	 * @param fontFormat
	 * 		字体类型，对于Java现阶段字体支持而言，支持{@link java.awt.Font#TRUETYPE_FONT ttf}和{@link java.awt.Font#TYPE1_FONT type1}
	 * @param fontPath
	 * 		字体文件所在路径，我们会自行查找{@link Files#file(String) file}，只需要给出相对路径就可以了
	 * @param style
	 * 		字体样式{@link java.awt.Font#PLAIN plain}/{@link java.awt.Font#BOLD bold}/{@link java.awt.Font#ITALIC italic}
	 * @param size
	 * 		字体大小
	 */
	public AWTFont(int fontFormat, String fontPath, int style, int size) {
		this(fontFormat, fontPath, style, size, null);
	}
	/**
	 * 从磁盘文件创建一个字体
	 * <br>
	 * 为了加速程序运行，或者是特定情况下不需要加载字符集中所有字体时，我们提供只加载部分字符的功能
	 * <br>
	 * 使用不带<code>additionChars</code>的构造函数时默认加载“{@link #CJK_unified_ideographs 中日韩统一表意文字}”
	 * <br>不过构造函数不会进行字符的操作，请自行调用对应函数进行加载{@link #loadCharacters(FontLoadProgressChangedEventListener) loadCharacters}
	 * 
	 * @param additionChars
	 * 		需要加载的字符
	 */
	public AWTFont(int fontFormat, String fontPath, int style, int size, String additionChars) {
		try {
			awtFont = java.awt.Font.createFont(fontFormat, Files.file(fontPath));
			if(style != java.awt.Font.PLAIN)
				awtFont.deriveFont(style, size);
			else awtFont.deriveFont(size);
		} catch (FontFormatException ex) {
			JMir2Exception exception = JMir2Exception.valueOf(ex, JMir2Exception.EXCEPTION_TYPE_CALC);
			log.error(exception.getMessage(), ex);
			throw exception;
		} catch (IOException ex) {
			JMir2Exception exception = JMir2Exception.valueOf(ex, JMir2Exception.EXCEPTION_TYPE_FILESTREAM);
			log.error(exception.getMessage(), ex);
			throw exception;
		}
		if(additionChars != null)
			this.customChars = additionChars.toCharArray();
		else {
			this.customChars = CJK_unified_ideographs;
		}
	}
	
	/** 从初始化好的字体中加载字体<br>通过调用{@link #isReadyForUse()}可以知道是否加载完成 */
	public synchronized void loadCharacters(FontLoadProgressChangedEventListener listener) {
		if(initialized || loading) return; // 不能多次加载
		CharactersLoadThread charsLoadThread = new CharactersLoadThread();
		charsLoadThread.m_listener = listener;
		loading = true;
		charsLoadThread.start();
	}
	
	/**
	 * 标识该字体是否已加载完成，可以使用了
	 * <br>调用{@link #loadCharacters(FontLoadProgressChangedEventListener)}后通过此方法来判断是否完成加载
	 * 
	 * @return 字体是否已经可以使用
	 */
	public boolean isReadyForUse() {
		return initialized;
	}
	
	protected void finalize() throws Throwable {
		if(fontTextures != null)
			for(Texture fontTex : fontTextures)
				fontTex.release();
	}
	
	/**
	 * 字符加载线程
	 * 
	 * @author ShawRyan
	 */
	private class CharactersLoadThread extends Thread {
		/** 进度监听 */
		FontLoadProgressChangedEventListener m_listener;
		/** 已加载的字符数 */
		private volatile float loaded = 0;
		/* 纹理数据 */
		private final List<BufferedImage> bis = new ArrayList<BufferedImage>(6);
		
		CharactersLoadThread() {
			setDaemon(true);
			setName("Font-Load-Thread");
		}
		
		public void run() {
			List<Character> customCharsArray = new ArrayList<Character>();
			for(int i = 0; i < AWTFont.this.customChars.length; ++i) {
				if(AWTFont.this.customChars[i] >= 256)
					customCharsArray.add(AWTFont.this.customChars[i]);
			}
			final int customCharsLength = customCharsArray.size();
			
			BufferedImage bi  = new BufferedImage(TEXTURE_WIDTH, TEXTURE_HEIGHT, BufferedImage.TYPE_4BYTE_ABGR);
			bis.add(bi);
			Graphics2D g = (Graphics2D) bi.getGraphics();
			g.setColor(Font.transparent);
			g.fillRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);
			
			int rowHeight = 0;
			int positionX = 0;
			int positionY = 0;
			
			for (int i = 0; i < 256 + customCharsLength; i++) {
				// 加载字体前256个字符(兼容ASCII)
				char ch = ( i < 256 ) ? (char) i : customCharsArray.get(i-256);
				BufferedImage fontImage = getFontImage(ch);
				TextureChar newChar = new TextureChar();
				newChar.width = fontImage.getWidth();
				newChar.height = fontImage.getHeight();
				if (positionX + newChar.width >= TEXTURE_WIDTH) {
					positionX = 0;
					positionY += rowHeight;
					rowHeight = 0;
				}
				if(positionY + rowHeight >= TEXTURE_HEIGHT) {
					bi  = new BufferedImage(TEXTURE_WIDTH, TEXTURE_HEIGHT, BufferedImage.TYPE_4BYTE_ABGR);
					bis.add(bi);
					g = (Graphics2D) bi.getGraphics();
					g.setColor(Font.transparent);
					g.fillRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);
					
					rowHeight = 0;
					positionX = 0;
					positionY = 0;
					
					Application.get().postRunnable(new Runnable() {
						public void run() {
							// 存入显存
							BufferedImage bi = bis.get(bis.size() - 2); // 因为新加入了一个对象，多减去1
							byte[] data = ((DataBufferByte) bi.getRaster().getDataBuffer()).getData();
							ByteBuffer buffer = ByteBuffer.allocateDirect(data.length);
							buffer.order(ByteOrder.nativeOrder());
							int pixelCount = data.length / 4;
							for(int i = 0; i < pixelCount; ++i) {
								// 将ARGB换成RGBA
								byte r = data[i * 4 + 1];
								byte g = data[i * 4 + 2];
								byte b = data[i * 4 + 3];
								byte a = data[i * 4];
								buffer.put(r).put(g).put(b).put(a);
							}
							buffer.flip();
							/* 绑定数据 */
							Texture tex = new Texture2DFontImpl(GL11.glGenTextures(), AWTFont.TEXTURE_WIDTH, AWTFont.TEXTURE_HEIGHT);
							GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
							GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
					        if(GLContext.getCapabilities().GL_EXT_texture_mirror_clamp) {
					        	GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, EXTTextureMirrorClamp.GL_MIRROR_CLAMP_TO_EDGE_EXT);
					        	GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, EXTTextureMirrorClamp.GL_MIRROR_CLAMP_TO_EDGE_EXT);
					        } else {
					        	GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP);
					        	GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP);
					        }
							
							// 绑定纹理数据到纹理id
							tex.bind();
					        GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, AWTFont.TEXTURE_WIDTH, AWTFont.TEXTURE_HEIGHT, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer);
					        AWTFont.this.fontTextures.add(tex);
							m_listener.progressChange(loaded / (customCharsLength + 256));
							if(loaded == customCharsLength + 256) {
								AWTFont.this.initialized = true;
								AWTFont.this.loading = false;
							}
						}
					});
				}
				newChar.storedX = positionX;
				newChar.storedY = positionY;
				newChar.storeTextureIndex = bis.size() - 1;
				if (newChar.height > fontHeight) {
					fontHeight = newChar.height;
				}
				if (newChar.height > rowHeight) {
					rowHeight = newChar.height;
				}
				// 将字体数据转移到纹理图中
				g.drawImage(fontImage, positionX, positionY, null);
				positionX += newChar.width;
				loaded++;
				AWTFont.this.chars.put( new Character( ch ), newChar );
				fontImage = null;
			}
			
			// 将最后一个静态的字符集图片存进显存(这种情况是一定会出现的！！！不会出现不执行的情况，也就是说其实无需进行下方的判断。。。)
			//if(font.fontTextures.size() != bis.size()) {
				Application.get().postRunnable(new Runnable() {
					public void run() {
						// 存入显存
						BufferedImage bi = bis.get(bis.size() - 1);
						byte[] data = ((DataBufferByte) bi.getRaster().getDataBuffer()).getData();
						ByteBuffer buffer = ByteBuffer.allocateDirect(data.length);
						buffer.order(ByteOrder.nativeOrder());
						int pixelCount = data.length / 4;
						for(int i = 0; i < pixelCount; ++i) {
							// 将ARGB换成RGBA
							byte r = data[i * 4 + 1];
							byte g = data[i * 4 + 2];
							byte b = data[i * 4 + 3];
							byte a = data[i * 4];
							buffer.put(r).put(g).put(b).put(a);
						}
						buffer.flip();
						/* 绑定数据 */
						Texture tex = new Texture2DFontImpl(GL11.glGenTextures(), AWTFont.TEXTURE_WIDTH, AWTFont.TEXTURE_HEIGHT);
						GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
						GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
				        if(GLContext.getCapabilities().GL_EXT_texture_mirror_clamp) {
				        	GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, EXTTextureMirrorClamp.GL_MIRROR_CLAMP_TO_EDGE_EXT);
				        	GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, EXTTextureMirrorClamp.GL_MIRROR_CLAMP_TO_EDGE_EXT);
				        } else {
				        	GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP);
				        	GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP);
				        }
						
						// 绑定纹理数据到纹理id
						tex.bind();
				        GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, AWTFont.TEXTURE_WIDTH, AWTFont.TEXTURE_HEIGHT, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer);
				        AWTFont.this.fontTextures.add(tex);
						m_listener.progressChange(loaded / (customCharsLength + 256));
						if(loaded == customCharsLength + 256) {
							AWTFont.this.initialized = true;
						}
					}
				});
				AWTFont.this.customChars = null;
			//}
		}
		
		/** 生成字符的纹理图 */
		private final BufferedImage getFontImage(char ch) {
			// 创建临时位图来获取字符宽度和高度
			BufferedImage tempfontImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
			Graphics2D g = (Graphics2D) tempfontImage.getGraphics();
			g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			g.setFont(AWTFont.this.awtFont);
			FontMetrics fontMetrics = g.getFontMetrics();
			int charwidth = fontMetrics.charWidth(ch);

			if (charwidth <= 0) {
				charwidth = 1;
			}
			int charheight = fontMetrics.getHeight();
			if (charheight <= 0) {
				charheight = AWTFont.this.awtFont.getSize();
			}

			// 创建真正承载字符色彩的位图
			BufferedImage fontImage;
			fontImage = new BufferedImage(charwidth, charheight, BufferedImage.TYPE_INT_ARGB);
			Graphics2D gt = (Graphics2D) fontImage.getGraphics();
			gt.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			gt.setFont(AWTFont.this.awtFont);

			gt.setColor(Color.WHITE);
			int charx = 0;
			int chary = 0;
			gt.drawString(String.valueOf(ch), (charx), (chary) + fontMetrics.getAscent());

			return fontImage;
		}
	}

	/**
	 * 使用纹理缓存的字符
	 * 
	 * @author ShawRyan
	 */
	private class TextureChar {
		/** 字符宽度 */
		public int width;
		/** 字符高度 */
		public int height;
		/** 字符存储的x坐标(左上角) */
		public int storedX;
		/** 字符存储的y坐标(左上角) */
		public int storedY;
		/** 存储的纹理图的索引 */
		public int storeTextureIndex;
	}
	
	/**
	 * 字体纹理绑定类
	 * 
	 * @author ShawRyan
	 */
	private class Texture2DFontImpl implements Texture {

		/** 宽度 */
		private int width;
		/** 高度 */
		private int height;
		/** 纹理ID */
		private int textureId;
		
		Texture2DFontImpl(int textureId, int width, int height) {
			this.textureId = textureId;
			this.width = width;
			this.height = height;
		}
		
		@Override
		public Integer getTextureRef() {
			return null;
		}

		@Override
		public void bind() {
			GL11.glBindTexture(GL11.GL_TEXTURE_2D, this.textureId);
		}

		@Override
		public void release() {
			/*ByteBuffer temp = ByteBuffer.allocateDirect(4);
		    temp.order(ByteOrder.nativeOrder());
		    IntBuffer texBuf = temp.asIntBuffer();
		    texBuf.put(this.textureId);
	        texBuf.flip();
	        glDeleteTextures(texBuf);*/
			GL11.glDeleteTextures(this.textureId);
		}
		
		public int getHeight() {
			return this.height;
		}

		public int getWidth() {
			return this.width;
		}

		public int getTextureID() {
			return this.textureId;
		}
	}
	
	public String getFontFace() {
		return awtFont.getFontName();
	}

	public int getLineHeight() {
		return fontHeight;
	}

	public boolean isBold() {
		return awtFont.isBold();
	}

	public boolean isItalic() {
		return awtFont.isItalic();
	}

	public int getFontSize() {
		return awtFont.getSize();
	}

	public int getWidth(String str) {
		int totalwidth = 0;
		TextureChar charObject = null;
		char currentChar = 0;
		for (int i = 0; i < str.length(); i++) {
			currentChar = str.charAt(i);
			charObject = chars.get( new Character( currentChar ) );
			
			if( charObject != null )
				totalwidth += charObject.width;
		}
		return totalwidth;
	}

	public void drawString(int x, int y, String text) {
		drawString(x, y, text, Font.white);
	}

	public void drawString(int x, int y, String text, Color color) {
		GL11.glColor4f(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
		TextureChar intObject = null;
		char charCurrent;
		int totalwidth = 0;
		for (int i = 0; i < text.length(); i++) {
			charCurrent = text.charAt(i);
			intObject = chars.get( new Character( charCurrent ) );
			
			if( intObject != null ) {
				gl.drawTexture2DNormalAllowColor(this.fontTextures.get(intObject.storeTextureIndex), 
						x + totalwidth, y, intObject.width, intObject.height,
						intObject.storedX, intObject.storedY, intObject.width, intObject.height);
				totalwidth += intObject.width;
			}
		}
	}
}